/*
 *  Copyright 2016 http://www.kedacomframework.org
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 */

package com.kedacom.ctsp.authz.oauth2.service;


import com.kedacom.ctsp.authz.exception.UnauthorizedException;
import com.kedacom.ctsp.authz.oauth2.OAuth2ServerProperties;
import com.kedacom.ctsp.authz.oauth2.core.vo.UserQueryParam;
import com.kedacom.ctsp.authz.oauth2.entity.AccessToken;
import com.kedacom.ctsp.lang.mapper.factory.EntityFactory;
import com.kedacom.ctsp.serialize.ISerialization;
import com.kedacom.ctsp.serialize.SerializationUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Assert;

import java.util.concurrent.TimeUnit;

/**
 * @author zhouhao
 */
public class AccessTokenServiceImpl implements AccessTokenService {

    @Autowired
    private TokenGenerator tokenGenerator;
    @Autowired
    private RedisTemplate<String, byte[]> redisTemplate;
    @Autowired
    private OAuth2ServerProperties serverProperties;
    @Autowired
    private EntityFactory entityFactory;
    @Autowired
    private ISerialization serialization;

    private static final String REDIS_ACCESS_TOKEN_PREFIX = "_commons_oauth2_access_token_";
    private static final String REDIS_REFRESH_TOKEN_PREFIX = "_commons_oauth2_refresh_token_";
    private static final String REDIS_CLIENT_FORMAT = "_commons_oauth2_client_%s_%s_%s";

    @Override
    public AccessToken createToken() {
        AccessToken accessEntity = entityFactory.newInstance(AccessToken.class);
        accessEntity.setAccessToken(tokenGenerator.generate());
        accessEntity.setRefreshToken(tokenGenerator.generate());
        accessEntity.setCreateTime(System.currentTimeMillis());
        return accessEntity;
    }

    @Override
    public AccessToken getTokenByRefreshToken(String refreshToken) {
        Assert.notNull(refreshToken, "refreshToken can not be null!");
        return get(REDIS_REFRESH_TOKEN_PREFIX + refreshToken);
    }

    @Override
    public AccessToken getTokenByAccessToken(String accessToken) {
        Assert.notNull(accessToken, "accessToken can not be null!");
        return get(REDIS_ACCESS_TOKEN_PREFIX + accessToken);
    }

    private AccessToken get(String key) {
        byte[] data = redisTemplate.opsForValue().get(key);
        if (data != null) {
            return SerializationUtil.deserialize(serialization, data);
        }
        return null;
    }

    /**
     * 检查accessToken是否存在
     *
     * @param param
     * @return
     */
    @Override
    public AccessToken checkAccessToken(UserQueryParam param) {
        AccessToken accessToken = getTokenByAccessToken(param.getAccessToken());
        if (null == accessToken) {
            throw new UnauthorizedException();
        }
        return accessToken;
    }

    /**
     * 保存AcessToken到redis
     *
     * @param token
     * @return
     */
    @Override
    public AccessToken saveOrUpdateToken(AccessToken token) {
        Assert.notNull(token, "token can not be null!");

        String key = String.format(REDIS_CLIENT_FORMAT, token.getClientId(), token.getGrantType(), token.getOwnerId());
        String accessToken = token.getAccessToken();
        String refreshToken = token.getRefreshToken();

        AccessToken oAuth2AccessToken = get(key);
        if (oAuth2AccessToken != null) {
            redisTemplate.delete(REDIS_REFRESH_TOKEN_PREFIX + accessToken);
            redisTemplate.delete(REDIS_ACCESS_TOKEN_PREFIX + refreshToken);
        }
        byte[] data = SerializationUtil.serialize(serialization, token);
        redisTemplate.opsForValue().set(key, data);
        redisTemplate.opsForValue()
                .set(REDIS_REFRESH_TOKEN_PREFIX + refreshToken, data, serverProperties.getRefreshTokenExpireSeconds(), TimeUnit.SECONDS);
        redisTemplate.opsForValue()
                .set(REDIS_ACCESS_TOKEN_PREFIX + accessToken, data, serverProperties.getAccessTokenExpireSeconds(), TimeUnit.SECONDS);

        return token;
    }

    /**
     * 清除accessToken相关信息
     *
     * @param token
     */
    @Override
    public void removeAccessToken(AccessToken token) {
        String key = String.format(REDIS_CLIENT_FORMAT, token.getClientId(), token.getGrantType(), token.getOwnerId());

        AccessToken oAuth2AccessToken = get(key);
        if (oAuth2AccessToken != null) {
            String accessToken = oAuth2AccessToken.getAccessToken();
            String refreshToken = oAuth2AccessToken.getRefreshToken();
            redisTemplate.delete(REDIS_REFRESH_TOKEN_PREFIX + accessToken);
            redisTemplate.delete(REDIS_ACCESS_TOKEN_PREFIX + refreshToken);
            redisTemplate.delete(key);
        }
    }
}
